import 'dart:io';
import 'dart:async';

class FillingBar {
  final String desc;
  final int total;
  final bool time;
  final bool percentage;
  final double scale;
  final int width;

  int _current = 0;
  
late DateTime _startTime;
  FillingBar({
    required this.desc,
    required this.total,
    this.time = true,
    this.percentage = true,
    this.scale = 0.2,
    this.width = 200,
  }) {
     _startTime = DateTime.now(); 
  }

  void increment() {
    _current++;
    _updateProgress();
  }

  void _updateProgress() {
    final filled = (_current / total * width).floor();
    final empty = width - filled;
    final bar = '█' * filled + '.' * empty;
    final percent = (_current / total * 100).toStringAsFixed(1);
    final elapsed = DateTime.now().difference(_startTime);
    final remaining = (elapsed.inMilliseconds / _current * (total - _current)).toInt();
    final elapsedStr = _formatDuration(elapsed);
    final remainingStr = _formatDuration(Duration(milliseconds: remaining));

    stdout.write('\r${desc}: $bar $_current/$total $percent% [ ${elapsedStr} / ${remainingStr} ]');
  }

  String _formatDuration(Duration duration) {
    final hours = duration.inHours;
    final minutes = (duration.inMinutes % 60).toString().padLeft(2, '0');
    final seconds = (duration.inSeconds % 60).toString().padLeft(2, '0');
    final milliseconds = (duration.inMilliseconds % 1000).toString().padLeft(3, '0');
    return '$hours:$minutes:$seconds.$milliseconds';
  }

  set total(int value) {
    this.total = value;
    _updateProgress();
  }

  set desc(String value) {
    this.desc = value;
    _updateProgress();
  }
}

void main(List<String> args) async {
  // A bar that looks like
  //
  // Loading:████████████████████████████████████████.................... 673/1000 67.3% [ 0:00:13.28 / 0:00:06.45 ]
  //
  final p = FillingBar(
    desc: "Loading",
    total: 1000,
    time: true,
    percentage: true,
    scale: 0.2,
    width: 200,
  );

  for (var i = 0; i < 500; i++) {
    p.increment();
    await Future.delayed(Duration(milliseconds: 10));
  }

  // Change the description and total mid-way
  await Future.delayed(Duration(seconds: 2));
  p.total = 750;
  p.desc = "Processing";
  await Future.delayed(Duration(seconds: 2));

  for (var i = 0; i < 250; i++) {
    p.increment();
    await Future.delayed(Duration(milliseconds: 5));
  }

  print('\nDone');
}